﻿using System;
using System.Linq;
using System.Windows.Input;
using Microsoft.Phone.Controls;
using WindowsPhoneEssentials.Commands;
using WindowsPhoneMvp.Commands;
using WindowsPhoneMvp.Navigation.Parsing;
using System.Windows.Navigation;

namespace WindowsPhoneMvp.Navigation
{
    /// <summary>
    /// Implements the navigation service for WP7
    /// </summary>
    public sealed class Wp7NavigationService : INavigationService
    {
        private readonly PhoneApplicationFrame frame;
        private readonly IViewFinder viewFinder;
        private readonly INavigationParser navigationParser;
        private bool preventJournaling = false;

        public Wp7NavigationService(PhoneApplicationFrame frame,
            IViewFinder viewFinder,
            INavigationParser navigationParser)
        {
            this.frame = frame;
            this.viewFinder = viewFinder;
            this.navigationParser = navigationParser;
        }

        private Wp7NavigationService(PhoneApplicationFrame frame,
            IViewFinder viewFinder,
            INavigationParser navigationParser,
            bool preventJournaling) : this(frame, viewFinder, navigationParser)
        {
            this.preventJournaling = preventJournaling;
        }

        private void ToView(string viewName, NavigationData navigationData)
        {
            var uri = new Uri(string.Format("{0}{1}",
                            viewFinder.ViewPath(viewName), 
                            navigationParser.FromData(navigationData)), UriKind.Relative);
            if (frame.BackStack.Any(item => item.Source.ToString().TrimStart('/') == uri.ToString().TrimStart('/')))
                throw new CircularNavigationException("The destination Uri already exists in the backstack.");

            if(preventJournaling)
            {
                frame.Navigated += RemoveLastBackEntry;
            }
            frame.Navigate(uri);
        }

        private void RemoveLastBackEntry(object sender, NavigationEventArgs e)
        {
            frame.RemoveBackEntry();
            frame.Navigated -= RemoveLastBackEntry;
        }

        public ICommand ByCommand<TView>() where TView : IView
        {
            var type = ResolveViewType<TView>();
            return ByCommand(type.Name);
        }

        public ICommand BackByCommand()
        {
            return new DelegateCommand(Back);
        }

        public void ToView(string viewName)
        {
            ToView(viewName, (NavigationData)null);
        }

        public void ToView<TView>() where TView : IView
        {
            var type = ResolveViewType<TView>();
            ToView(type.Name);
        }

        public void ToView<TView>(Action<NavigationData> parameterBuilder) where TView : IView
        {
            var type = ResolveViewType<TView>();
            var navData = NavigationData.For(type.Name);
            parameterBuilder(navData);
            ToView(navData);           
        }

        void ToView(NavigationData navigationData)
        {
            ToView(navigationData.View, navigationData);
        }

        public void ToView(string viewName, Action<NavigationData> parameterBuilder)
        {
            var navData = NavigationData.For(viewName);
            parameterBuilder(navData);
            ToView(viewName, navData);
        }

        public void Back()
        {
            if (frame.CanGoBack)
                frame.GoBack();
        }

        public void BackToView<TView>() where TView : IView
        {
            if(frame.CanGoBack)
            {
                var backStack = frame.BackStack.ToList();
                foreach(var item in backStack)
                {
                    string itemUrl = item.Source.ToString();
                    var uri = UriFor<TView>().ToString();
                    uri = CheckUri<TView>(uri, ref itemUrl);
                    if(itemUrl.StartsWith(uri) == false)
                    {
                        frame.RemoveBackEntry();
                    }
                }
                Back();
            }
            else
            {
                new Wp7NavigationService(frame, viewFinder, navigationParser, true).ToView<TView>();
            }
        }

        private static string CheckUri<TView>(string uri, ref string itemUrl) where TView : IView
        {
            if (uri.StartsWith("//") == false)
                uri = "/" + uri;
            if (itemUrl.StartsWith("//") == false)
                itemUrl = "/" + itemUrl;
            return uri;
        }

        public INavigationService WithoutBack()
        {
            return new Wp7NavigationService(frame, viewFinder, navigationParser, true);
        }

        public void RemoveBackEntry()
        {
            frame.RemoveBackEntry();
        }

        public void RemoveBackEntry<TView>() where TView : IView
        {
            var backStack = frame.BackStack.ToList();
            foreach(var item in backStack)
            {
                string itemUrl = item.Source.ToString();
                var uri = UriFor<TView>().ToString();
                uri = CheckUri<TView>(uri, ref itemUrl);
                if(itemUrl.StartsWith(uri))
                {
                    frame.RemoveBackEntry();
                    break;
                }
            }
        }

        public Uri UriFor<TView>() where TView : IView
        {
            return UriFor<TView>(_ => { });
        }

        public Uri UriFor<TView>(Action<NavigationData> parameterBuilder) where TView : IView
        {
            var type = ResolveViewType<TView>();
            var navigationData = new NavigationData(type.Name);
            parameterBuilder(navigationData);
            return new Uri(string.Format("{0}{1}",
                                  viewFinder.ViewPath(type.Name),
                                  navigationParser.FromData(navigationData)), UriKind.Relative);
        }

        private ICommand ByCommand(string view, NavigationData navigationData,
            Action preExecutionAction, Func<bool> canExecute)
        {
            var navCommand = new NavigationCommand(view, this, navigationData, preExecutionAction, canExecute);
            return navCommand;
        }

        public ICommand ByCommand<TView>(Action<NavigationData> parameterBuilder) where TView : IView
        {
            var type = ResolveViewType<TView>();
            var navCommand = new NavigationCommand(type.Name, this);
            Action preExecuteAction = () =>
                {
                    var navData = NavigationData.For(type.Name);
                    parameterBuilder(navData);
                    navCommand.WithNavigationData(navData);
                };
            navCommand.WithPreExecutionAction(preExecuteAction);
            return navCommand;
        }

        public ICommand ByCommand(string view)
        {
            return ByCommand(view, new NavigationData(view), null, null);
        }

        Type ResolveViewType<TView>() where TView : IView
        {
            var type = typeof(TView);
            if(typeof(TView).IsInterface)
            {
                type = viewFinder.FromInterface<TView>();
            }
            return type;
        }
    }

}
